package case20

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"log"
	"os"
	"time"
)

func Test() {
	fmt.Println("🎯示例二十：Preload预加载关联查询及高级预加载技巧实现")

	db, err := mysqlConnect()
	if err != nil {
		fmt.Println(err)
		return
	}

	// 自动迁移创建表结构
	if err := db.AutoMigrate(&ClassCase20{}, &TeacherCase20{}, &StudentCase20{}); err != nil {
		fmt.Println("自动迁移创建表结构失败：", err)
		return
	}

	//// 1.插入数据
	//// 创建班级数据
	//cArr1 := []ClassCase20{
	//	{Name: "一年级"},
	//	{Name: "二年级"},
	//	{Name: "三年级"},
	//}
	//db.Create(&cArr1)
	//
	//// 创建老师数据
	//tArr1 := []TeacherCase20{
	//	{Name: "张老师"},
	//	{Name: "唐老师"},
	//	{Name: "汤老师"},
	//	{Name: "谭老师"},
	//	{Name: "堂老师"},
	//}
	//db.Create(&tArr1)

	//// 2.再创建学生数据
	//sArr1 := []StudentCase20{
	//	{Name: "一明", ClassID: 1},
	//	{Name: "一红", ClassID: 1},
	//	{Name: "二名", ClassID: 2},
	//	{Name: "二洪", ClassID: 2},
	//	{Name: "三谭", ClassID: 3},
	//}
	//db.Create(&sArr1)

	//// 查询班级
	//cArr2 := []ClassCase20{}
	//db.Order("id asc").Find(&cArr2)
	//fmt.Println(cArr2)
	//// 查询老师
	//tArr2 := []TeacherCase20{}
	//db.Order("id asc").Find(&tArr2)
	//fmt.Println(tArr2)
	//// 查询学生
	//sArr2 := []StudentCase20{}
	//db.Order("id asc").Find(&sArr2)
	//fmt.Println(sArr2)
	//
	//fmt.Println(cArr2[0], tArr2[0], sArr2[0])
	//
	//// 3.建立表关系
	//// 一年级 关联 学生： 一明，一红
	//db.Model(&cArr2[0]).Association("Students").Append(&sArr2[0], &sArr2[1])
	//// 二年级 关联 学生： 二名，二洪
	//db.Model(&cArr2[1]).Association("Students").Append(&sArr2[2], &sArr2[3])
	//// 三年级 关联 学生： 三谭
	//db.Model(&cArr2[2]).Association("Students").Append(&sArr2[4])
	//
	//// 一年级 关联 老师：张老师 唐老师
	//db.Model(&cArr2[0]).Association("Teachers").Append(&tArr2[0], &tArr2[1])
	//// 二年级 关联 老师：汤老师 谭老师
	//db.Model(&cArr2[1]).Association("Teachers").Append(&tArr2[2], &tArr2[3])
	//// 三年级 关联 老师：堂老师
	//db.Model(&cArr2[2]).Association("Teachers").Append(&tArr2[4])
	//
	//// 老师：张老师 唐老师  关联 班级：一年级
	//db.Model(&tArr2[0]).Association("Classes").Append(&cArr2[0])
	//db.Model(&tArr2[1]).Association("Classes").Append(&cArr2[0])
	//// 老师：汤老师 谭老师  关联 班级：二年级
	//db.Model(&tArr2[2]).Association("Classes").Append(&cArr2[1])
	//db.Model(&tArr2[3]).Association("Classes").Append(&cArr2[1])
	//// 老师：堂老师  关联 班级：三年级
	//db.Model(&tArr2[4]).Association("Classes").Append(&cArr2[2])
	//
	//// 老师 张老师,唐老师 关联 学生：一明，一红
	//db.Model(&tArr2[0]).Association("Students").Append(&sArr2[0], &sArr2[1])
	//db.Model(&tArr2[1]).Association("Students").Append(&sArr2[0], &sArr2[1])
	//// 老师 汤老师 谭老师 关联 学生： 二名，二洪
	//db.Model(&tArr2[2]).Association("Students").Append(&sArr2[2], &sArr2[3])
	//db.Model(&tArr2[3]).Association("Students").Append(&sArr2[2], &sArr2[3])
	//// 老师：堂老师  关联 关联 学生： 三谭
	//db.Model(&tArr2[4]).Association("Students").Append(&sArr2[4])

	// 预加载查询
	// 查询班级，预加载学生和老师
	var cArr3 []ClassCase20
	db.Preload("Students").Preload("Teachers").Find(&cArr3)
	for _, c := range cArr3 {
		fmt.Println("班级：", c.Name)
		for _, s := range c.Students {
			fmt.Println("\t 学生：", s.Name)
		}
		for _, t := range c.Teachers {
			fmt.Println("\t 老师：", t.Name)
		}
	}

	fmt.Println("--------------------------------------------------")

	// 或使用嵌套预加载 查询班级，预加载学生(包含学生的老师)和老师
	var cArr4 []ClassCase20
	db.Preload("Students.Teachers").Preload("Teachers").Find(&cArr4)
	for _, c := range cArr4 {
		fmt.Println("班级：", c.Name)
		for _, s := range c.Students {
			fmt.Println("\t 学生：", s.Name)
			for _, st := range s.Teachers {
				fmt.Println("\t\t 老师：", st.Name)
			}
		}
		for _, t := range c.Teachers {
			fmt.Println("\t 老师：", t.Name)
		}
	}

	fmt.Println("--------------------------------------------------")

	// 查询指定班级的所有学生和老师
	var cArr5 []ClassCase20
	db.Preload("Students").Preload("Teachers").Find(&cArr5, 1)
	for _, c := range cArr5 {
		fmt.Println("班级：", c.Name)
		for _, s := range c.Students {
			fmt.Println("\t 学生：", s.Name)
		}
		for _, t := range c.Teachers {
			fmt.Println("\t 老师：", t.Name)
		}
	}

	fmt.Println("--------------------------------------------------")

	// 查询学生所在老师和班级
	var sArr3 []StudentCase20
	db.Preload("Teachers").Preload("Class").Find(&sArr3)
	for _, s := range sArr3 {
		fmt.Println("学生：", s.Name)
		fmt.Println("\t 班级：", s.Class.Name)
		for _, t := range s.Teachers {
			fmt.Println("\t 老师：", t.Name)
		}
	}

	fmt.Println("--------------------------------------------------")

	// 查询老师授课的班级和学生
	var tArr3 []TeacherCase20
	db.Preload("Classes").Preload("Students").Find(&tArr3)
	for _, t := range tArr3 {
		fmt.Println("老师：", t.Name)
		for _, st := range t.Students {
			fmt.Println("\t 学生：", st.Name)
		}
		for _, sc := range t.Classes {
			fmt.Println("\t 班级：", sc.Name)
		}
	}

	fmt.Println("--------------------------------------------------")

	// 统计班级中的老师数量
	var res1 []map[string]interface{}
	db.
		Model(&ClassCase20{}).
		Select(`class_case20.name, count(0) AS 'num'`).
		Joins(`INNER JOIN class_teacher ON class_case20.id = class_teacher.class_case20_id`).
		Group(`class_case20.name`).
		Scan(&res1)
	//SELECT class_case20.name, count(0) AS 'num' FROM `class_case20` INNER JOIN class_teacher ON class_case20.id = class_teacher.class_case20_id GROUP BY `class_case20`.`name`
	fmt.Println(res1)

	fmt.Println("--------------------------------------------------")

	// 统计班级中的学生数量
	var res2 []map[string]interface{}
	db.Raw(`
		SELECT  class_case20.name, count(0) AS 'num'
		FROM class_case20
		INNER JOIN class_student on class_case20.id = class_student.class_case20_id
		GROUP BY class_case20.name
		`).
		Scan(&res2)
	fmt.Println(res2)

	fmt.Println("--------------------------------------------------")

	// 统计班级中老师数量最少的班级
	var res3 []map[string]interface{}
	db.Raw(`
		SELECT  class_case20.name, count(0) AS 'num'
		FROM class_case20
		INNER JOIN class_teacher on class_case20.id = class_teacher.class_case20_id
		GROUP BY class_case20.name ORDER BY num asc limit 1
		`).
		Scan(&res3)
	fmt.Println(res3)

	fmt.Println("--------------------------------------------------")

	// 查询张老师带领的学生列表
	var res4 []map[string]interface{}
	db.Model(&TeacherCase20{}).
		Select("teacher_case20.name , student_case20.name as 'studentName'").
		Joins(`
			INNER JOIN teacher_student ON teacher_case20.id = teacher_student.teacher_case20_id
			INNER JOIN student_case20 ON student_case20.id = teacher_student.student_case20_id
			`).
		Where("teacher_case20.name = ?", "张老师").
		Scan(&res4)
	fmt.Println(res4)

	fmt.Println("--------------------------------------------------")

	// 高级预加载技巧实现
	// 计算学生和老师的数量
	var cArr6 []ClassCase20
	db.
		Preload("Students").
		Preload("Teachers").
		Joins("LEFT JOIN class_student ON class_case20.id = class_student.class_case20_id").
		Joins("LEFT JOIN class_teacher ON class_case20.id = class_teacher.class_case20_id").
		Group("class_case20.id").
		Select(`class_case20.*, 
                COUNT(DISTINCT class_student.student_case20_id) as student_count,
                COUNT(DISTINCT class_teacher.teacher_case20_id) as teacher_count`).
		Find(&cArr6)
	fmt.Println(cArr6)

	fmt.Println("--------------------------------------------------")

	// 高级预加载技巧实现
	// 预加载中增加查询条件
	var c7 ClassCase20
	db.
		Preload("Teachers", func(db *gorm.DB) *gorm.DB {
			return db.Where("name = ?", "张老师")
		}).
		Preload("Students", func(db *gorm.DB) *gorm.DB {
			return db.Where("name like ?", "一%")
		}).
		Find(&c7, 1)
	fmt.Println(c7)

	fmt.Println(db)
}

// 初始化连接
func mysqlConnect() (*gorm.DB, error) {
	url := "root:123456@tcp(127.0.0.1:3306)/case1?charset=utf8mb4&parseTime=True&loc=Local"

	// 配置自定义 Logger 打印sql日志
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold:             time.Second, // 慢 SQL 阈值
			LogLevel:                  logger.Info, // 可以设置为不同级别 Silent-不打印任何日志 Error-只打印错误日志 Warn-打印慢查询和错误 Info-打印慢查询和错误
			IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound（记录未找到）错误
			Colorful:                  true,        // 彩色打印
		},
	)

	db, err := gorm.Open(mysql.Open(url), &gorm.Config{
		Logger: newLogger, // 设置 Logger
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 关闭默认，使用表名
		},
	})
	if err != nil {
		return nil, fmt.Errorf("mysql connect err: %v", err)
	}

	sqlDB, err := db.DB()
	if err != nil {
		panic("failed to get sql.DB")
	}

	// 配置连接池
	// 最大空闲连接数 - 应该小于最大打开连接数
	sqlDB.SetMaxIdleConns(20)
	// 最大打开连接数 - 根据应用负载和MySQL配置调整
	sqlDB.SetMaxOpenConns(100)
	// 连接最大生命周期 - 小于MySQL的wait_timeout设置
	sqlDB.SetConnMaxLifetime(5 * time.Minute)
	// 空闲连接最大存活时间
	sqlDB.SetConnMaxIdleTime(10 * time.Minute)

	return db, nil
}

// 多表关联结构体
// 一个班级 对应 多个学生；一个学生 对应 多个老师；一个老师 对应 多个班级；

// 班级结构体
type ClassCase20 struct {
	ID   int    `gorm:"primarykey;autoIncrement"`
	Name string `gorm:"type:varchar(50);unique"`
	// 预加载实现方式
	//一个班级对应多个学生 关系
	Students []StudentCase20 `gorm:"many2many:class_student;"` //class_student 自动创建关系表AutoMigrate
	//一个班级对应不同老师 关系
	Teachers []TeacherCase20 `gorm:"many2many:class_teacher;"` //class_teacher 自动创建关系表AutoMigrate
}

// 老师结构体
type TeacherCase20 struct {
	ID   int    `gorm:"primarykey;autoIncrement"`
	Name string `gorm:"type:varchar(50);unique"`
	//一个老师对应多个学生 关系
	Students []StudentCase20 `gorm:"many2many:teacher_student;"`
	//一个老师对应多个班级 关系
	Classes []ClassCase20 `gorm:"many2many:class_teacher;"`
}

// 学生结构体
type StudentCase20 struct {
	ID   int    `gorm:"primarykey;autoIncrement"`
	Name string `gorm:"type:varchar(50);unique"`
	//一个学生对应多个老师
	Teachers []TeacherCase20 `gorm:"many2many:teacher_student;"`
	//一个学生对应一个班级  关系
	ClassID int
	Class   ClassCase20 `gorm:"foreignKey:ClassID"` // 一个学生对应一个班级
}
